﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using Microsoft.TeamFoundation.WorkItemTracking.Client;

public partial class _Default : System.Web.UI.Page
{
    private const string DefaultKanbanConfigName = "default";

    private KanbanConfig _kanbanConfig;
    private TfsWorkItems _workItemStore;

    protected void Page_Load(object sender, EventArgs e)
    {
        // Get the Kanban config section name from the url
        string configName = Request["configName"];
        if (string.IsNullOrEmpty(configName))
        {
            configName = DefaultKanbanConfigName;
        }

        // Store the configName so it can be used in calls to GetUserList
        AssignedToAutoComplete.ContextKey = configName;

        // Load the Kanban Config and get the requested config section
        KanbanConfigRepository kanbanConfigRepository = new KanbanConfigRepository();
        _kanbanConfig = kanbanConfigRepository[configName];

        // Create the work item store (used to access the work items)
        var tpc = new TfsCollection(_kanbanConfig.TfsServerUri, _kanbanConfig.TfsCollectionName);
        _workItemStore = tpc.WorkItems;

        if (!IsPostBack)
        {
            List<string> areaPaths = tpc.GetAreas(_kanbanConfig.TfsProjectName);
            areaPaths.Insert(0, _kanbanConfig.TfsProjectName);
            AreaList.DataSource = areaPaths;
            AreaList.DataBind();

            List<string> iterationPaths = tpc.GetIterations(_kanbanConfig.TfsProjectName);
            iterationPaths.Insert(0, _kanbanConfig.TfsProjectName);
            IterationList.DataSource = iterationPaths;
            IterationList.DataBind();

            LoadQueryFilters();

            //
            // Set the URL for the cumulative flow report.
            //
            string reportUri = _kanbanConfig.CumulativeFlowUri;
            if (!String.IsNullOrWhiteSpace(reportUri))
                CumulativeReportFrame.Attributes["src"] = reportUri + "&rs:Command=Render&rc:Toolbar=false";
        }

        CreateBoard();

        SaveQueryFilters();
    }

    [WebMethod]
    [ScriptMethod]
    public static string[] GetUserList(string prefixText, int count, string contextKey)
    {
        KanbanConfigRepository kanbanConfigRepository = new KanbanConfigRepository();
        var kanbanConfig = kanbanConfigRepository[contextKey];

        var tpc = new TfsCollection(kanbanConfig.TfsServerUri, kanbanConfig.TfsCollectionName);
        List<string> users = tpc.GetUsers();
        List<string> filteredUsers = users.FindAll(
            delegate(string user)
            {
                return user.StartsWith(prefixText);
            });

        int range = count < filteredUsers.Count ? count : filteredUsers.Count;
        return filteredUsers.GetRange(0, range).ToArray();
    }

    private void CreateBoard()
    {
        // Get the list of work item types that we should show at the top level into a string array.

        var witToCss = new Dictionary<string, string>();
        foreach (WorkItemConfig wiConfig in _kanbanConfig.WorkItemConfigs)
        {
            witToCss[wiConfig.Name] = wiConfig.CssClass;
        }

        // Add additional WHERE clauses for user, iteration, and area if present
        var whereClause = new StringBuilder();
        AddClause(whereClause, "AND Source.[System.AssignedTo] = '{0}' ", AssignedToTextBox.Text);
        AddClause(whereClause, "AND Source.[System.IterationPath] UNDER '{0}' ", IterationList.SelectedValue);
        AddClause(whereClause, "AND Source.[System.AreaPath] UNDER '{0}' ", AreaList.SelectedValue);

        // Retrieve the tree of work items and then display them on the board.
        WorkItemInfo[] workItems = _workItemStore.GetWorkItemTree(_kanbanConfig.TfsProjectName, _kanbanConfig.WorkItemConfigs, whereClause.ToString(), _kanbanConfig.BlockedField);
        CreateLanes(workItems, witToCss);
    }

    private void CreateLanes(WorkItemInfo[] workItems, Dictionary<string, string> witToCss)
    {
        // For each LaneDefinition...
        foreach (LaneDefinition laneDefition in _kanbanConfig.LaneDefinitions)
        {
            // Load a LaneControl user control
            LaneControl control = (LaneControl)LoadControl("LaneControl.ascx");

            control.Name = laneDefition.Name;
            control.WipLimit = laneDefition.WipLimit;
            control.WipFloor = laneDefition.WipFloor;

            // Find which TFS workflow states that are mapped to this LaneDefintion...
            foreach (LaneState tfsState in laneDefition.LaneStates)
            {
                // Find the work items that are in this TFS state
                List<WorkItemView> stateWorkItemViews = BuildCards(workItems, tfsState.Name, witToCss);

                // Add the matching work items to the appropriate column on the Lane control
                if (tfsState.LaneType == LaneType.InProgress)
                {
                    control.InProgressItems = stateWorkItemViews;
                    control.LeftHeader = (tfsState.DisplayName == null) ? tfsState.Name : tfsState.DisplayName;
                }
                else if (tfsState.LaneType == LaneType.Queue)
                {
                    control.QueueItems = stateWorkItemViews;
                    control.RightHeader = (tfsState.DisplayName == null) ? tfsState.Name : tfsState.DisplayName;
                }
            }

            LanesPlaceholder.Controls.Add(control);
        }
    }

    /// <summary>
    ///		This method builds a list of work item "cards" that will be shown in a column.
    /// </summary>
    /// <param name="workItems">The entire list of work items for the board</param>
    /// <param name="stateName">The name of the state to use as a filter for work items in the current column</param>
    /// <returns>A list of work item "cards" for work items in the column</returns>
    private List<WorkItemView> BuildCards(WorkItemInfo[] workItems, string stateName, Dictionary<string, string> witToCss)
    {
        List<WorkItemView> stateWorkItemViews = new List<WorkItemView>();

        foreach (WorkItemInfo info in workItems)
        {
            WorkItem workItem = info.WorkItem;

            if (workItem.State == stateName)
            {
                string cssClass = "card-UserStory";
                witToCss.TryGetValue(workItem.Type.Name, out cssClass);

                // Build the data that will be used by the CardControl to display the information.
                WorkItemView workItemView = new WorkItemView
                {
                    Id = workItem.Id,
                    Type = workItem.Type.Name,
                    Title = workItem.Title,
                    AreaPath = workItem.AreaPath,
                    AssignedTo = Convert.ToString(workItem.Fields["System.AssignedTo"].Value),
                    CssClass = cssClass,
                    WorkItemUrl = _workItemStore.FormatWorkItemEditUrl(workItem.Id)
                };

                AggregateTasks(info, workItemView);
                SetAreaColor(info, workItemView);

                if (workItem.Fields.Contains("State Change Date") == true)
                {
                    if (workItem["State Change Date"] != null)
                    {
                        DateTime stateChangeDate = (DateTime)workItem["State Change Date"];
                        workItemView.Age = (DateTime.Now - stateChangeDate).Days;
                    }
                }

                stateWorkItemViews.Add(workItemView);
            }
        }

        return stateWorkItemViews;
    }

    private void SetAreaColor(WorkItemInfo info, WorkItemView workItemView)
    {
        workItemView.AreaColor = (from AreaInfo areaInfo in _kanbanConfig.AreaInfos
                                  where workItemView.AreaPath.StartsWith(areaInfo.Path, StringComparison.CurrentCultureIgnoreCase)
                                  select areaInfo.Color).FirstOrDefault();
    }

    private void AggregateTasks(WorkItemInfo info, WorkItemView view)
    {
        // Create a Dictionary of Dictionaries to store the count of work items that are in the defined (in Kanban.config) states - set the inital count for state of each sub work item type to 0.
        var counts = new Dictionary<string, int>();

        foreach (string state in _kanbanConfig.ChildStates)
        {
            counts[state] = 0;
        }

        // Loop through each child work item and count the number in each defined state
        foreach (WorkItem child in info.Children)
        {
            if (counts.ContainsKey(child.State))
                counts[child.State]++;
            else
                counts[child.State] = 1;
        }

        var states = new List<StateCountView>();
        foreach (string state in _kanbanConfig.ChildStates)
        {
            states.Add(new StateCountView(state, counts[state]));
        }
        view.StateCounts = states.ToArray();
    }

    private void AddClause(StringBuilder query, string clause, string parameter)
    {
        if (string.IsNullOrWhiteSpace(parameter))
            return;

        query.AppendFormat(clause, parameter);
    }

    private void SaveQueryFilters()
    {
        HttpCookie cookie = new HttpCookie("QueryFilters");
        cookie.Expires = DateTime.Now.AddYears(1);
        cookie.Values["AssignedTo"] = AssignedToTextBox.Text;
        cookie.Values["IterationPath"] = IterationList.SelectedValue;
        cookie.Values["AreaPath"] = AreaList.SelectedValue;
        Response.Cookies.Add(cookie);
    }

    private void LoadQueryFilters()
    {
        AssignedToTextBox.Text = ReadCookieValue("QueryFilters", "AssignedTo");
        IterationList.SelectedValue = ReadCookieValue("QueryFilters", "IterationPath");
        AreaList.SelectedValue = ReadCookieValue("QueryFilters", "AreaPath");
    }

    private string ReadCookieValue(string cookieName, string subCookieName)
    {
        if (Request.Cookies[cookieName] == null)
            return string.Empty;

        return Request.Cookies[cookieName][subCookieName];
    }
}
